Анализ оттока клиентов банка «Метанпромбанк»¶

Нам предоставлены данные заказчиком в лице менеджмента банка «Метанпромбанк». Наша задача проанализировать клиентов банка и выделить сегменты клиентов, которые склонны уходить из банка, для дальнейшей работы и принятия решений на основе этой информации и наших выводов менеджментом.

Декомпозиция задач¶

  1. Загрузка данных и библиотек для работы

  2. Изучение данных

    • Изучение общей информации по столбцам
    • Поиск дубликатов
    • Изучение пустот
      • Изучаем столбец age
  3. EDA

    • Изучение данных по столбцам, их значений и выбросов
      • Изучение городов
      • Изучение возраста
      • Изучение баланса
      • Изучение количества продуктов
      • Изучение данных скоринга
      • Изучение количества клиентов по полу
      • Изучение количества клиентов по наличию кредитной карты
    • Влияние показателей на отток
    • Корреляция показателей
    • Сегментация пользователей по параметру оттока
  4. Проверка гипотез

    • Гипотеза №1
    • Гипотеза №2
  5. Выводы и рекомендации

Загрузка данных и библиотек¶

In [1]:
# установим phik

! pip install phik
Collecting phik
  Downloading phik-0.12.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (686 kB)
     |████████████████████████████████| 686 kB 1.3 MB/s eta 0:00:01
Requirement already satisfied: matplotlib>=2.2.3 in /opt/conda/lib/python3.9/site-packages (from phik) (3.3.4)
Requirement already satisfied: scipy>=1.5.2 in /opt/conda/lib/python3.9/site-packages (from phik) (1.9.1)
Requirement already satisfied: pandas>=0.25.1 in /opt/conda/lib/python3.9/site-packages (from phik) (1.2.4)
Requirement already satisfied: joblib>=0.14.1 in /opt/conda/lib/python3.9/site-packages (from phik) (1.1.0)
Requirement already satisfied: numpy>=1.18.0 in /opt/conda/lib/python3.9/site-packages (from phik) (1.21.1)
Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (2.8.1)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (8.4.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (1.4.4)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (2.4.7)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (0.11.0)
Requirement already satisfied: pytz>=2017.3 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.25.1->phik) (2021.1)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.9/site-packages (from python-dateutil>=2.1->matplotlib>=2.2.3->phik) (1.16.0)
Installing collected packages: phik
Successfully installed phik-0.12.4
In [2]:
#загрузим библиотеки
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
import scipy.stats as stats
import warnings
from plotly import graph_objects as go
from scipy import stats as st
import plotly.express as px
import warnings; warnings.filterwarnings(action = 'ignore')
import phik
from phik import resources, report

#загрузим предоставленные заказчиком данные
data = pd.read_csv('https://code.s3.yandex.net/datasets/bank_scrooge.csv')
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 12 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   USERID         10000 non-null  int64  
 1   score          10000 non-null  float64
 2   city           10000 non-null  object 
 3   gender         10000 non-null  object 
 4   age            9974 non-null   float64
 5   equity         10000 non-null  int64  
 6   balance        7705 non-null   float64
 7   products       10000 non-null  int64  
 8   credit_card    10000 non-null  int64  
 9   last_activity  10000 non-null  int64  
 10  EST_SALARY     10000 non-null  float64
 11  churn          10000 non-null  int64  
dtypes: float64(4), int64(6), object(2)
memory usage: 937.6+ KB

При первом рассмотрении мы видим, что нам потребуется перевести названия к общему виду. Также мы видим, что в полях возраста, и баланса отсутствует часть данных, балансовые данные отсутствуют почти в 25% строк, что делает их "сброс" невозможным, а выводить баланс по среднему или медиане будет неверным решением, поэтому на данные по балансу мы не будем никак воздействовать, а вот данные по возрасты мы изучим и, возможно, сбросим.

In [3]:
#переименуем столбцы для удобства дальнейшей работы
data.rename(columns={"USERID": "user_id", "EST_SALARY": "est_salary"}, inplace=True)

Изучение данных¶

Изучение общей информации по столбцам¶

In [4]:
display(data.head(5))
user_id score city gender age equity balance products credit_card last_activity est_salary churn
0 183012 850.0 Рыбинск Ж 25.0 1 59214.82 2 0 1 75719.14 1
1 146556 861.0 Рыбинск Ж 37.0 5 850594.33 3 1 0 86621.77 0
2 120722 892.0 Рыбинск Ж 30.0 0 NaN 1 1 1 107683.34 0
3 225363 866.0 Ярославль Ж 51.0 5 1524746.26 2 0 1 174423.53 1
4 157978 730.0 Ярославль М 34.0 5 174.00 1 1 0 67353.16 1

Опишем имеющиеся у нас столбцы:

  • user_id - уникальный идентификатор пользователя банка
  • score - баллы кредитного скоринга (больше = выше, лучше)
  • city - город
  • gender - пол (М/Ж)
  • age - возраст
  • equity - количество баллов собственности
  • balance - баланс
  • products - количество банковских продуктов
  • credit_card - есть ли кредитная карта
  • last_activity - активный ли клиент
  • est_salary - оценочный доход клиента
  • churn - признак оттока(уходит/не уходит)
In [5]:
#повторно выведем информацию о столбцах
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 12 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   user_id        10000 non-null  int64  
 1   score          10000 non-null  float64
 2   city           10000 non-null  object 
 3   gender         10000 non-null  object 
 4   age            9974 non-null   float64
 5   equity         10000 non-null  int64  
 6   balance        7705 non-null   float64
 7   products       10000 non-null  int64  
 8   credit_card    10000 non-null  int64  
 9   last_activity  10000 non-null  int64  
 10  est_salary     10000 non-null  float64
 11  churn          10000 non-null  int64  
dtypes: float64(4), int64(6), object(2)
memory usage: 937.6+ KB
In [6]:
#изучим возраст
data['age'].describe()
Out[6]:
count    9974.000000
mean       42.734409
std        12.179971
min        18.000000
25%        33.000000
50%        40.000000
75%        51.000000
max        86.000000
Name: age, dtype: float64

Поиск дубликатов¶

In [7]:
data.duplicated().sum()
Out[7]:
0
In [8]:
# прямых дубликатов не обнаружили, проверим по городу и ID
data.duplicated(subset=['user_id', 'city']).sum()
Out[8]:
0
In [9]:
# прямых дубликатов также нет, тогда посмотрим на дубликаты по ID
data.duplicated(subset=['user_id']).sum()
Out[9]:
73
In [10]:
#изучим их внимательнее
display(data[data.duplicated(subset=['user_id'], keep=False)].sort_values(by='user_id'))
user_id score city gender age equity balance products credit_card last_activity est_salary churn
1893 116540 883.0 Рыбинск Ж 55.0 1 362756.49 3 0 1 175920.48 1
7694 116540 887.0 Ярославль Ж 38.0 0 NaN 1 0 1 119247.61 0
7542 117943 880.0 Ярославль Ж 40.0 0 NaN 1 1 0 137718.93 0
4866 117943 855.0 Рыбинск Ж 32.0 6 1036832.93 4 1 1 107792.71 1
5896 120258 905.0 Ярославль М 30.0 0 NaN 1 1 1 146427.96 0
... ... ... ... ... ... ... ... ... ... ... ... ...
2597 226719 990.0 Ярославль М 37.0 4 14648692.14 2 0 0 934412.61 1
8205 227795 840.0 Рыбинск М 34.0 2 350768.03 1 1 0 102036.14 1
8497 227795 839.0 Ярославль М 34.0 2 326593.14 2 1 0 103314.92 0
6457 228075 839.0 Рыбинск М 39.0 5 507199.85 3 0 1 85195.80 0
1247 228075 932.0 Ярославль М NaN 5 7601719.20 2 1 1 408121.16 0

146 rows × 12 columns

Вывод из поиска дубликатов:
Дубликаты среду уникальных идентификаторов, конечно, есть, но их ничтожно мало (менее 1%), а также мы видим на представленных данных, что дубликаты эти - это одни и те же пользователи услуг банка, но с продуктами банка из разных городов, думаю, что мы можем оставить эти данные, как они есть, чтобы учитывать их в расчётах.

Изучение пустот¶

Из общих данных по столбцам мы видим, что данные отсутствуют в двух столбца age и balance. balance отсутствует почти у 25% данных, что делает его удаление из данных невозможным, слишком большой других данных о пользователях мы потеряем, а вот age не так много, давайте его изучим.

Изучение столбца age¶

In [11]:
#сколько точно клиентов без возраста в базе
data[data['age'].isnull()].count()
Out[11]:
user_id          26
score            26
city             26
gender           26
age               0
equity           26
balance          10
products         26
credit_card      26
last_activity    26
est_salary       26
churn            26
dtype: int64
In [12]:
#посмотрим на них внимательнее
data[data['age'].isnull()]
Out[12]:
user_id score city gender age equity balance products credit_card last_activity est_salary churn
1247 228075 932.0 Ярославль М NaN 5 7601719.20 2 1 1 408121.16 0
2165 187635 692.0 Рыбинск Ж NaN 0 NaN 1 1 1 160368.82 0
2444 221156 913.0 Ярославль М NaN 0 NaN 1 1 1 135693.24 0
3091 138660 836.0 Ростов Ж NaN 5 294315.53 2 0 1 63310.22 1
4912 210674 834.0 Рыбинск М NaN 1 238330.52 2 0 1 93775.06 0
5470 218868 827.0 Рыбинск Ж NaN 4 448959.07 2 1 1 67835.95 0
5495 151662 884.0 Рыбинск Ж NaN 0 NaN 1 1 1 137500.77 0
7236 210135 908.0 Рыбинск Ж NaN 4 1120340.31 3 1 1 85002.15 0
7248 219343 920.0 Рыбинск Ж NaN 0 NaN 1 1 0 159248.67 0
7345 184913 829.0 Ярославль Ж NaN 3 188648.77 2 0 1 75206.90 0
7409 214031 777.0 Ярославль М NaN 2 171510.23 1 1 1 75409.63 0
8015 198635 670.0 Ярославль Ж NaN 0 NaN 1 1 1 168699.33 0
8070 226550 940.0 Рыбинск М NaN 0 NaN 1 0 1 147696.95 0
8293 216848 930.0 Ярославль М NaN 0 NaN 1 1 1 199542.51 0
8385 206759 915.0 Рыбинск М NaN 0 NaN 1 1 0 71179.53 0
8449 210898 805.0 Ярославль Ж NaN 0 NaN 1 0 1 922080.25 0
8632 221197 893.0 Ярославль М NaN 0 NaN 1 1 0 173929.92 0
8785 127440 663.0 Ярославль М NaN 0 NaN 1 1 1 117197.56 0
9104 222480 776.0 Рыбинск Ж NaN 5 796735.09 1 1 1 55073.63 0
9301 202983 942.0 Рыбинск Ж NaN 0 NaN 1 1 1 163804.73 0
9380 187459 894.0 Рыбинск М NaN 0 NaN 1 1 0 178012.28 0
9457 141945 929.0 Ярославль М NaN 0 NaN 1 1 0 381868.89 0
9632 185829 927.0 Ярославль М NaN 0 NaN 1 1 0 231254.86 0
9634 221809 917.0 Ярославль М NaN 0 NaN 1 1 1 192644.15 0
9667 163657 849.0 Ярославль М NaN 4 1254013.85 2 1 1 119106.67 0
9819 140934 832.0 Рыбинск Ж NaN 3 385763.16 2 0 1 59651.35 0
In [13]:
# изучим балансы среди тех клиентов, что у нас без возраста
print(data[data['age'].isnull()]['balance'].sum())
data[data['age'].isnull()]['balance'].sum()/data[data['age'].isnull()]['balance'].count()
12500335.73
Out[13]:
1250033.573
In [14]:
# изучим их совокупный и средний доход
print(data[data['age'].isnull()]['est_salary'].sum())
data[data['age'].isnull()]['est_salary'].sum()/data[data['age'].isnull()]['est_salary'].count()
4643215.18
Out[14]:
178585.1992307692

Вывод из поиска пустых значений:
Судя, по всему, наши 26 людей без возрастов довольно обеспеченные клиенты, ведь их расчетный средний доход 175к рублей, а баланс среди тех, кто указан около 1,25кк рублей, оставим эти строки, чтобы не упустить эти данные в будущем анализе. А также немаловажно отметить, что среди них всего 1 клиент, который планирует уходить, остальные держат деньги на счетаъ и/или могут в любой момент вернуться в банк, что интересно для нас в силу их ежемесячного заработка.

Вывод из изучения данных:
Данные готовы к использованию, дальнейшая их предобработка не потребуется, названия мы перевели в начале этой главы, чтобы было удобнее работать, age не можем снести из-за больших объёмов денег этих клиентов, а balance из-за слишком большого объёма данных

EDA¶

Изучение данных по столбцам, их значений и выбросов¶

In [15]:
#быстро визуализироваем имеющиеся данные и их столбцы
data.hist(figsize=(10,10));
No description has been provided for this image

Изучение городов¶

In [16]:
#изучим города в данных
data['city'].unique()
Out[16]:
array(['Рыбинск', 'Ярославль', 'Ростов'], dtype=object)
In [17]:
data.groupby('city')['user_id'].count().reset_index()
Out[17]:
city user_id
0 Ростов 1417
1 Рыбинск 2695
2 Ярославль 5888

В наших данных представлены 3 города: Ростов, Рыбинск и Ярославль. Большая часть клиентов приходится на Ярославль, на втором месте Рыбинск и замыкает эту тройку Ростов.

Изучение возраста¶

In [18]:
print(data['age'].nunique())
data['age'].describe()
68
Out[18]:
count    9974.000000
mean       42.734409
std        12.179971
min        18.000000
25%        33.000000
50%        40.000000
75%        51.000000
max        86.000000
Name: age, dtype: float64
In [19]:
data['age'].hist(figsize=(15,5));
No description has been provided for this image

Изучив показатели по возрасту клиентов банка, мы видим, что большинство наших пользователей приходится на 25-45лет.

Изучение баланса¶

In [20]:
# для удобства считывания данных введём новый столбец с показателем баланса в млн. рублей
data['balance_mln'] = data['balance']/1000000
# изучим полученные данные
display(data['balance_mln'].describe())
data['balance_mln'].hist(figsize=(15,5));
count    7705.000000
mean        0.827794
std         1.980614
min         0.000000
25%         0.295554
50%         0.524272
75%         0.980706
max       119.113552
Name: balance_mln, dtype: float64
No description has been provided for this image

Вывод из изучения данных о балансе:
Среднее значение баланка приходится приблизительно на 828т.р., квартильные значения считаются между 295к и 980к рублей. Стоит отметить, что у одного из пользоваетелей банка есть на балансе более 119млн. рублей.

Изучение количества продуктов¶

In [21]:
# изучим количество клиентов банка по количеству продуктов на клиента
display(data.groupby('products')['user_id'].count().reset_index().sort_values(by='user_id'))
products user_id
0 0 1
5 5 19
4 4 474
3 3 1039
1 1 3341
2 2 5126
In [22]:
#смотрим на пользователя без банковских продуктов
display(data[data['products']==0])
user_id score city gender age equity balance products credit_card last_activity est_salary churn balance_mln
8957 147837 962.0 Рыбинск Ж 79.0 3 NaN 0 0 0 25063.96 1 NaN
In [23]:
# убираем пользователя из данных
data = data[data['products']!=0]
display(data[data['products']==0])
user_id score city gender age equity balance products credit_card last_activity est_salary churn balance_mln

Вывод из изучения данных по количеству продуктов:
Большинство пользователей имеют 2 банковских продукта, реже 1, и около 1500 клиентов имеют 3 и более продукта, кстати, среди данных был пользователь без продуктов, его мы удалим, т.к. там не самый интересный для нас клиент и на основе предыдущих критериев, так ещё и без продуктов банка совсем.

Изучение данных скоринга¶

In [24]:
display(data['score'].describe())
data['score'].hist(figsize=(15,5));
count    9999.000000
mean      848.688069
std        65.441981
min       642.000000
25%       802.000000
50%       853.000000
75%       900.000000
max      1000.000000
Name: score, dtype: float64
No description has been provided for this image

Выводы из изучения скоринга клиентов:
Среднее значение скоринга около 850, межквартильный размах от Q1 до Q3 скоринга между 802 и 900.

Изучение количества клиентов по полу¶

In [25]:
# изучим количество клиентов банка по полу
display(data.groupby('gender')['user_id'].count().reset_index().sort_values(by='user_id'))
fig = go.Figure(data=[go.Pie(labels=data['gender'], values=data['user_id'])])
fig.show() 
gender user_id
0 Ж 4994
1 М 5005

Выводы из изучения пола клиентов:
Количество клиентов в датасете распределено по полу практически поровну, с минимальным отклонением в сторону мужской половины.

Изучение количества клиентов по наличию кредитной карты¶

In [26]:
# изучим количество клиентов банка по полу
display(data.groupby('credit_card')['user_id'].count().reset_index().sort_values(by='user_id'))
fig = go.Figure(data=[go.Pie(labels=data['credit_card'], values=data['user_id'])])
fig.show() 
credit_card user_id
0 0 3195
1 1 6804

Выводы из изучения наличия кредитного продукта среди клиентов:
Количество клиентов в датасете по кредитному продукту распределено приблизительно, как 2/3 к 1/3, а именно, более 68% клиентов являются пользователями кредитных продуктов.

Влияние показателей на отток¶

In [27]:
print('Пользователей уходит(%): ',round(data[data['churn'] == 1]['user_id'].count() / data['user_id'].count() * 100,2))

#Изучим средние значения показателей по двум группам (остающихся и уходящих)
churn_rate = data.groupby('churn').agg('mean').T
churn_rate['perc_diff'] = round(((churn_rate[1] - churn_rate[0]) / churn_rate[0]) * 100,1)
display(churn_rate)
Пользователей уходит(%):  18.19
churn 0 1 perc_diff
user_id 172004.359046 1.709751e+05 -0.6
score 845.428362 8.633469e+02 2.1
age 43.020846 4.142959e+01 -3.7
equity 2.374817 3.764156e+00 58.5
balance 733982.585648 1.133993e+06 54.5
products 1.757579 2.377130e+00 35.3
credit_card 0.709169 5.514019e-01 -22.2
last_activity 0.483741 7.025838e-01 45.2
est_salary 147783.200108 1.483107e+05 0.4
balance_mln 0.733983 1.133993e+00 54.5

Рассмотрим каждый показатель в отдельности:

  • Скоринг - отличается незначительно, всего около 2%
  • Возраст - отличается незначительно, около 4%
  • Собственность - значительно отличается, разница почти 60% между группами
  • Баланс(млн.) - значительно отличается, разница почти 55% между группами
  • Количество продуктов - значительно отличается, разница около 35% между группами
  • Наличие кредитного продукта - значительно отличается, разница около 22% между группами
  • Активность - значительно отличается, разница около 45% между группами
  • Расчетный доход -отличается незначительно, всего около 0,5%

Итог из таблицы:
Главными критериями различия между группами являются количество используемых банковских продуктов, баланс на счету, рейтинг собственности и их активность, а именно - уходящие клиенты менее активны, имеют значительно меньший баланс,

In [28]:
# функция для построения гистограмм
def def_plots(col, title,bins=15):
    fig, ax = plt.subplots(figsize=(15, 5))

    sns.histplot(data=data,
             hue='churn',
             x=col,
             stat='density',
             common_norm=False,
             palette='Pastel2',
             bins=bins,
             ax=ax)

    ax.set_xlabel(col)
    ax.set_ylabel('Распределение клиентов')
    ax.set_title(title);
In [29]:
def_plots('score','Распределение скоринга среди ушедших и оставшихся пользователей')
No description has been provided for this image
In [30]:
fig, ax = plt.subplots(figsize=(15, 5))
plt.xlim(0,3)
sns.histplot(data=data,
             hue='churn',
             x='balance_mln',
             stat='density',
             common_norm=False,
             palette='Pastel2',
             bins=1500,
             ax=ax)

ax.set_xlabel('Балланс, млн.руб.')
ax.set_ylabel('Распределение клиентов')
ax.set_title('Распределение баланса среди ушедших и оставшихся пользователей');
No description has been provided for this image
In [31]:
def_plots('age','Распределение возраста среди ушедших и оставшихся пользователей',bins=50)
No description has been provided for this image
In [32]:
def_plots('equity','Распределение баллов недвижимости среди ушедших и оставшихся пользователей',bins=9)
No description has been provided for this image
In [33]:
def_plots('products','Распределение количества продуктов среди ушедших и оставшихся пользователей',bins=5)
No description has been provided for this image
In [34]:
def_plots('credit_card','Распределение наличия кредитной карты среди ушедших и оставшихся пользователей',bins=2)
No description has been provided for this image
In [35]:
def_plots('last_activity','Распределение активности среди ушедших и оставшихся пользователей',bins=3)
No description has been provided for this image
In [36]:
def_plots('gender','Распределение пола среди ушедших и оставшихся пользователей',bins=2)
No description has been provided for this image
In [37]:
def_plots('city','Распределение города клиента среди ушедших и оставшихся пользователей',bins=3)
No description has been provided for this image
In [38]:
def_plots('est_salary','Распределение предполагаемого дохода клиента среди ушедших и оставшихся пользователей',bins=30)
No description has been provided for this image

Вывод из исследования оттока:

  • Видна зависимость скоринга на отток клиентов (в районе 820-900 видим явный отток)
  • Чем больше денег на счету - тем вероятнее клиент уходит (видим отток, начиная от 700к и далее)
  • Чаще отток происходит у клиентов в возрасте от 25 до 35, а также между 50 и 60 годами.
  • Наличие 3 и более баллов недвижимости также признак оттока
  • Наличие более 2 продуктов прямо влияет на отток (видим, что пользователи с 2 и более продуктами наиболее подвержены отточности)
  • Наличие кредитного продукта положительно влияет на усидчивость клиентов
  • Активные клиенты уходят чаще, чем клиенты, которые давно не проявляли активность
  • Мужчины уходят чаще женщин
  • Чаще клиенты уходят в городе Ярославль
  • Клиенты с заработком более 100т.р. уходят чаще, чем клиенты с меньшим доходом.

Корреляция показателей¶

In [39]:
# построим хитмап для оценки корреляции показателей
fig, ax = plt.subplots(figsize=(14, 8))
data_corr = data.drop('user_id', axis=1)
corr_matrix = data_corr.phik_matrix()
sns.heatmap(corr_matrix, annot=True, ax=ax, cmap='Reds', fmt='.1%', alpha=0.8)
plt.show()
interval columns not set, guessing: ['score', 'age', 'equity', 'balance', 'products', 'credit_card', 'last_activity', 'est_salary', 'churn', 'balance_mln']
No description has been provided for this image

Выводы:

  • Больше остальных с целевой переменной коррелирует показатель баллов недвижимости (equity) - чем их больше, тем выше показатель оттока.
  • Следующим параметром по значимости коррелирует активность пользователей (activity) - чем пользователи активнее, тем выше показатель оттока.
  • Далее по значимости следует показатель количества баллов собственности (equity) - чем их больше, тем выше показатель оттока.

В целом матрица соответствует ранее сделанным выводам.

Сегментация пользователей по параметру оттока¶

In [40]:
# вспомним какой у нас средний показатель оттока
print('В среднем, согласно данным, уходит(%): ',round(data[data['churn'] == 1]['user_id'].count() / data['user_id'].count() * 100,0))
В среднем, согласно данным, уходит(%):  18.0

На основе прошлых данных соберём несколько сегментов и найдём те, которые дают наибольший отток

In [41]:
# 1 сегмент: мужчины с балансом выше 700к
segment_1 = data.query("gender == 'М' and balance_mln >= 0.7")['churn'].agg({'count','sum'}).round(2)
segment_1['%'] = round(segment_1['sum']/segment_1['count']*100,0)
print(segment_1)
sum       647.0
count    1461.0
%          44.0
Name: churn, dtype: float64

1 рассматриваемый сегмент это мужчины и балансом от 700т.р., мы видим, что таких клиентов в нашей базе почти 1500, и из них отток почти у 44%.

In [42]:
# 2 сегмент: мужчины с 2 и более продуктами и балансом более 700к
segment_2 = data.query("gender == 'М' and products >= 2 and balance_mln >= 0.7")['churn'].agg({'count','sum'}).round(2)
segment_2['%'] = round(segment_2['sum']/segment_2['count']*100,0)
print(segment_2)
sum       596.0
count    1260.0
%          47.0
Name: churn, dtype: float64

2 сегмент это клиенты-мужчины с 2 и более продуктами и балансом от 700т.р., таких клиентов в нашей базе около 1200, и из них отток у 47%.

In [43]:
# 3 сегмент: мужчины с 2 и более продуктами в возрасте до 35 лет
segment_3 = data.query("gender == 'М' and products >= 2 and age <= 35")['churn'].agg({'count','sum'}).round(2)
segment_3['%'] = round(segment_3['sum']/segment_3['count']*100,0)
print(segment_3)
sum       407.0
count    1218.0
%          33.0
Name: churn, dtype: float64

3 сегмент представлен мужчинами с 2 и более продуктами в возрасте до 35 лет(вкл.), в нашей базе больше 1200 таких клиентов, а отток у таких клиентов 33%.

In [44]:
# 4 сегмент: клиенты в возрасте от 18 до 30 и 2 и более продуктами
segment_4 = data.query("products >= 2 and age >= 18 and age <= 30")['churn'].agg({'count','sum'}).round(2)
segment_4['%'] = round(segment_4['sum']/segment_4['count']*100,0)
print(segment_4)
sum      276.0
count    949.0
%         29.0
Name: churn, dtype: float64

4 рассматриваемый сегмент один из самых, на мой взгляд, перспективных. Это клиенты в возраст от 18 до 30 лет, которые уже используют 2 и более продуктов банка. В базе почти 1000 подобных клиентов и отток у них 29%.

In [45]:
# 5 сегмент: мужчины в возрасте от 50 лет
segment_5 = data.query("gender == 'М' and age >= 50")['churn'].agg({'count','sum'}).round(2)
segment_5['%'] = round(segment_5['sum']/segment_5['count']*100,0)
print(segment_5)
sum       297.0
count    1000.0
%          30.0
Name: churn, dtype: float64

Наконец, 5 довольно отточный сегмент - это мужчины от 50 лет, которые при численности в 1000 клиентов в базе показывают отток в 297 клиентов(около 30%)

Проверка гипотез¶

Гипотеза №1¶

Первая гипотеза звучит как: различается ли значимо доход между теми клиентами, что ушли и теми, что остались.

Нулевая гипотеза (H0):
Доход между клиентами, которые ушли, и действующими клиентами, не различается значимо

Альтернативная гипотеза (H1):
Доход между клиентами, которые ушли, и действующими клиентами, различается значимо

In [46]:
alpha = .05   # критический уровень статистической значимости    
results = st.stats.ttest_ind(data[data['churn'] == 0]['est_salary'], 
                             data[data['churn'] == 1]['est_salary'])

print('p-значение: ', results.pvalue)    # тест двухсторонний

if results.pvalue < alpha:
    print("Отвергаем нулевую гипотезу")
else:
    print("Не получилось отвергнуть нулевую гипотезу")
p-значение:  0.8839364433181659
Не получилось отвергнуть нулевую гипотезу

По результатам теста мы видим, что не удалось опровергнуть гипотезу о различии дохода между теми клиентами, которые уходят и теми, которые остаются.

Гипотеза №2¶

Вторая гипотеза звучит как: различается ли значимо баланс между теми клиентами, что ушли и теми, что остались.

Нулевая гипотеза (H0):
Баланс между клиентами, которые ушли, и действующими клиентами, не различается значимо

Альтернативная гипотеза (H1):
Баланс между клиентами, которые ушли, и действующими клиентами, различается значимо

In [47]:
alpha = 0.05   # критический уровень статистической значимости    
group1 = data[data['churn'] == 0]['balance'].dropna()  # Значения 'balance' для churn=0
group2 = data[data['churn'] == 1]['balance'].dropna()  # Значения 'balance' для churn=1

results = st.ttest_ind(group1, group2)

print('p-значение:', results.pvalue)    # тест двухсторонний

if results.pvalue < alpha:
    print("Отвергаем нулевую гипотезу")
else:
    print("Не получилось отвергнуть нулевую гипотезу")
p-значение: 5.2961006573922e-14
Отвергаем нулевую гипотезу

По результатам теста гипотезы мы видим, что нулевую гипотезу об отсутствии различия в балансе между теми клиентами, которые уходят и теми, которые остаются, отвергаем.

Выводы и рекомендации¶

Мы исследовали базу, предоставленную нам заказчиком, после чего определели критерии отточности и по ним выстроили 5 сегментов клиентов, по которым ниже также дадим комментарии, после чего провели проверку двух гипотез, вероятно, влияющих на отточность клиентов.

В первую очередь стоит отметить параметры по которым видна явная отточность клиентов:

  • Видна зависимость скоринга на отток клиентов (в районе 820-900 видим явный отток)
  • Чем больше денег на счету - тем вероятнее клиент уходит (видим отток, начиная от 700к и далее)
  • Чаще отток происходит у клиентов в возрасте от 25 до 35, а также между 50 и 60 годами.
  • Наличие 3 и более баллов недвижимости также признак оттока
  • Наличие более 2 продуктов прямо влияет на отток (видим, что пользователи с 2 и более продуктами наиболее подвержены отточности)
  • Наличие кредитного продукта положительно влияет на усидчивость клиентов
  • Активные клиенты уходят чаще, чем клиенты, которые давно не проявляли активность
  • Мужчины уходят чаще женщин
  • Чаще клиенты уходят в городе Ярославль
  • Клиенты с заработком более 100т.р. уходят чаще, чем клиенты с меньшим доходом.

Выделенные сегменты и рекомендации по ним: 1 сегмент - мужчины с балансом более 700т.р.(в базе 1461 клиент подходящий образу, из них 647 клиентов уходит/ушло(44%))
На основе этого сегмента стоит рассмотреть программы мотивации, кэшбека и др. маркетинговые продукты для удержания группы, например, повышенный кэшбек на инструменты и заправки, а учитывая их баланс, возможно, предоставление специальных условий для клиентов с крупными депозитами, таких как повышенные процентные ставки по депозитам или индивидуальные инвестиционные консультации.

2 сегмент - мужчины с 2 и более продуктами и аналогичным балансом, которых не сильно меньше(1260, но при этом параметр отточности уже 47%). Рекомендации аналогичны предыдущим, НО поскольку увеличение количества продуктов коррелирует с ростом оттока, следует пересмотреть ассортимент предлагаемых услуг. Возможно, стоит сфокусироваться на улучшении качества имеющихся продуктов, а не на их количестве.

3 сегмент - мужчины в возрасте до 33 лет с 2 и более продуктами. Таких клиентов в нашей базе 1218, из которых 33% уходят(407 клиентов). Это говорит нам о том, что нам не удаётся заинтересовать молодую аудиторию мужчин, как и в первом сегменте, рекомендую пересмотреть политику маркетинговых акций для этого сегмента для их удержания, изучить их основные траты и предложить более выгодные условия.

4 сегмент - мужчины и женщины до 35 лет, включительно, с 2 и более продуктами. На мой взгляд очень перспективный сегмент клиентов, ведь это молодые люди, которые уже готовы использовать несколько продуктов банка, но мы видим, что среди них отточность более чем в 1.5 раза превышает среднюю отточность клиентов, а значит, что они не получают какие-то "плюшки" за выбор банка, которые могут предложить другие банки, возможно, что их интересует бесплатное обслуживание, прежположу, что можно рассмотреть сегменты: beauty, Авто, Досуг(Кафе, Рестораны, Бары) как зоны для дополнительной маркетинговой активности(кэшбеки, акции, доп. предложения за оплату именно картой "МетанПромБанка")

И, наконец, 5 сегмент - мужчины старше 50 лет, таких 10% в базе и из них отточность почти 30%, т.е. 3% всей нашей базы уходят именно в этом сегменте. Вероятно, таких клиентов могут заинтересовать акции и предложения по авто, магазаинам, досугу, покупке товаров для дома, а также возможности для инвестии своих накоплений и/или выгодные условия для вкладов и т.д.

Подытог по двум блокам: Следует внимательнее проанализировать и, возможно, пересмотреть продуктовую линейку банка, уделить внимание более детальной сегментации клиентов по скорингу, обратить внимание на предложения для молодых клиентов и клиентов с большим балансом на счетах банка.

Проверка гипотез:
Первая гипотеза один у нас предполагала, что есть значимая разница в доходах между теми клиентами, что остались и теми, что уходят. Гипотеза опровергнута - значимой разницы мы не обнаружили.

Второй гипотезой мы предположили, что баланс между уходящими и остающимися клиентами не различается значимо, но это теория опровергнута, а равно мы подтвердили свои наблюдения из блока EDA, где увидели, что клиенты с бОльшими балансами склонны к оттоку.